home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1865 / 1865.xpi / chrome / adblockplus.jar / content / ui / sidebar.js < prev    next >
Text File  |  2010-01-07  |  32KB  |  1,147 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Adblock Plus.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Wladimir Palant.
  18.  * Portions created by the Initial Developer are Copyright (C) 2006-2009
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *
  23.  * ***** END LICENSE BLOCK ***** */
  24.  
  25. var effectiveTLD = Cc["@mozilla.org/network/effective-tld-service;1"].getService(Ci.nsIEffectiveTLDService);
  26. var RequestList = abp.RequestList;
  27.  
  28. // Main browser window
  29. var mainWin = parent;
  30.  
  31. // The window handler currently in use
  32. var wndData = null;
  33.  
  34. var cacheSession = null;
  35. var noFlash = false;
  36.  
  37. // Matchers for disabled filters
  38. var disabledBlacklistMatcher = new abp.Matcher();
  39. var disabledWhitelistMatcher = new abp.Matcher();
  40.  
  41. var abpHooks = null;
  42.  
  43. function init() {
  44.     var list = E("list");
  45.     list.view = treeView;
  46.  
  47.     // Restore previous state
  48.     var params = abp.getParams();
  49.     if (params && params.search) {
  50.         E("searchField").value = params.search;
  51.         treeView.setFilter(params.search);
  52.     }
  53.     if (params && params.focus && E(params.focus))
  54.         E(params.focus).focus();
  55.     else
  56.         E("searchField").focus();
  57.  
  58.     var selected = null;
  59.     if (/sidebarDetached\.xul$/.test(parent.location.href)) {
  60.         mainWin = parent.opener;
  61.         mainWin.addEventListener("unload", mainUnload, false);
  62.         E("detachButton").hidden = true;
  63.         E("reattachButton").hidden = false;
  64.         if (!mainWin.document.getElementById("abp-sidebar"))
  65.             E("reattachButton").setAttribute("disabled", "true");
  66.         if (mainWin.document.getElementById("abp-key-sidebar")) {
  67.             var sidebarKey = mainWin.document.getElementById("abp-key-sidebar").cloneNode(true);
  68.             parent.document.getElementById("detached-keyset").appendChild(parent.document.importNode(sidebarKey, true));
  69.         }
  70.  
  71.         // Set default size/position unless already persisted
  72.         let defaults = {screenX: 0, screenY: 0, width: 600, height: 300};
  73.         if (params && params.position)
  74.             defaults = params.position;
  75.  
  76.         let wnd = parent.document.documentElement;
  77.         for (let attr in defaults)
  78.             if (!wnd.hasAttribute(attr))
  79.                 wnd.setAttribute(attr, defaults[attr]);
  80.     }
  81.  
  82.     abpHooks = mainWin.document.getElementById("abp-hooks");
  83.     window.__defineGetter__("content", function() {return abpHooks.getBrowser().contentWindow;});
  84.  
  85.     // Install item listener
  86.     RequestList.addListener(handleItemChange);
  87.  
  88.     // Initialize matchers for disabled filters
  89.     reloadDisabledFilters();
  90.     filterStorage.addFilterObserver(reloadDisabledFilters);
  91.     filterStorage.addSubscriptionObserver(reloadDisabledFilters);
  92.  
  93.     // Activate flasher
  94.     list.addEventListener("select", onSelectionChange, false);
  95.  
  96.     // Retrieve data for the window
  97.     wndData = RequestList.getDataForWindow(window.content);
  98.     treeView.setData(wndData.getAllLocations());
  99.     if (wndData.lastSelection) {
  100.         noFlash = true;
  101.         treeView.selectItem(wndData.lastSelection);
  102.         noFlash = false;
  103.     }
  104.  
  105.     // Install a handler for tab changes
  106.     abpHooks.getBrowser().addEventListener("select", handleTabChange, false);
  107. }
  108.  
  109. // To be called for a detached window when the main window has been closed
  110. function mainUnload() {
  111.     parent.close();
  112. }
  113.  
  114. // To be called on unload
  115. function cleanUp() {
  116.     if (!abp)
  117.         return;
  118.  
  119.     flasher.stop();
  120.     RequestList.removeListener(handleItemChange);
  121.     filterStorage.removeFilterObserver(reloadDisabledFilters);
  122.     filterStorage.removeSubscriptionObserver(reloadDisabledFilters);
  123.  
  124.     abpHooks.getBrowser().removeEventListener("select", handleTabChange, false);
  125.     mainWin.removeEventListener("unload", mainUnload, false);
  126. }
  127.  
  128. /**
  129.  * Updates matchers for disabled filters (global disabledBlacklistMatcher and
  130.  * disabledWhitelistMatcher variables), called on each filter change.
  131.  */
  132. function reloadDisabledFilters()
  133. {
  134.     disabledBlacklistMatcher.clear();
  135.     disabledWhitelistMatcher.clear();
  136.  
  137.     for each (let subscription in filterStorage.subscriptions)
  138.     {
  139.         if (subscription.disabled)
  140.             continue;
  141.  
  142.         for each (let filter in subscription.filters)
  143.             if (filter instanceof abp.RegExpFilter && filter.disabled)
  144.                 (filter instanceof abp.BlockingFilter ? disabledBlacklistMatcher : disabledWhitelistMatcher).add(filter);
  145.     }
  146. }
  147.  
  148. // Called whenever list selection changes - triggers flasher
  149. function onSelectionChange() {
  150.     var item = treeView.getSelectedItem();
  151.     if (item)
  152.         E("copy-command").removeAttribute("disabled");
  153.     else
  154.         E("copy-command").setAttribute("disabled", "true");
  155.     if (item && wndData)
  156.         wndData.lastSelection = item;
  157.  
  158.     if (!noFlash)
  159.         flasher.flash(item ? item.nodes : null);
  160. }
  161.  
  162. function handleItemChange(wnd, type, data, item) {
  163.     // Check whether this applies to us
  164.     if (wnd != window.content)
  165.         return;
  166.  
  167.     // Maybe we got called twice
  168.     if (type == "select" && data == wndData)
  169.         return;
  170.  
  171.     // If adding something from a new data container - select it
  172.     if (type == "add" && data != wndData)
  173.         type = "select";
  174.  
  175.     var i;
  176.     var filterSuggestions = E("suggestionsList");
  177.     if (type == "clear") {
  178.         // Current document has been unloaded, clear list
  179.         wndData = null;
  180.         treeView.setData([]);
  181.     }
  182.     else if (type == "select" || type == "refresh") {
  183.         // We moved to a different document, reload list
  184.         wndData = data;
  185.         treeView.setData(wndData.getAllLocations());
  186.     }
  187.     else if (type == "invalidate")
  188.         treeView.boxObject.invalidate();
  189.     else if (type == "add")
  190.         treeView.addItem(item);
  191. }
  192.  
  193. function handleTabChange() {
  194.     wndData = RequestList.getDataForWindow(window.content);
  195.     treeView.setData(wndData.getAllLocations());
  196.     if (wndData.lastSelection) {
  197.         noFlash = true;
  198.         treeView.selectItem(wndData.lastSelection);
  199.         noFlash = false;
  200.     }
  201. }
  202.  
  203. // Fills a box with text splitting it up into multiple lines if necessary
  204. function setMultilineContent(box, text, noRemove)
  205. {
  206.     if (!noRemove)
  207.         while (box.firstChild)
  208.             box.removeChild(box.firstChild);
  209.  
  210.     for (var i = 0; i < text.length; i += 80)
  211.     {
  212.         var description = document.createElement("description");
  213.         description.setAttribute("value", text.substr(i, 80));
  214.         box.appendChild(description);
  215.     }
  216. }
  217.  
  218. // Fill in tooltip data before showing it
  219. function fillInTooltip(e) {
  220.     var item;
  221.     if (treeView.data && !treeView.data.length)
  222.         item = treeView.getDummyTooltip();
  223.     else
  224.         item = treeView.getItemAt(e.clientX, e.clientY);
  225.  
  226.     if (!item)
  227.         return false;
  228.  
  229.     let filter = ("filter" in item && item.filter && !item.filter.disabled ? item.filter : null);
  230.     let size = ("tooltip" in item ? null : getItemSize(item));
  231.     let subscriptions = (filter ? filter.subscriptions.filter(function(subscription) { return !subscription.disabled; }) : []);
  232.  
  233.     E("tooltipDummy").hidden = !("tooltip" in item);
  234.     E("tooltipAddressRow").hidden = ("tooltip" in item);
  235.     E("tooltipTypeRow").hidden = ("tooltip" in item);
  236.     E("tooltipSizeRow").hidden = !size;
  237.     E("tooltipDocDomainRow").hidden = ("tooltip" in item || !item.docDomain);
  238.     E("tooltipFilterRow").hidden = !filter;
  239.     E("tooltipFilterSourceRow").hidden = !subscriptions.length;
  240.  
  241.     if ("tooltip" in item)
  242.         E("tooltipDummy").setAttribute("value", item.tooltip);
  243.     else
  244.     {
  245.         E("tooltipAddress").parentNode.hidden = (item.typeDescr == "ELEMHIDE");
  246.         setMultilineContent(E("tooltipAddress"), item.location);
  247.     
  248.         var type = item.localizedDescr;
  249.         if (filter && filter instanceof abp.WhitelistFilter)
  250.             type += " " + E("tooltipType").getAttribute("whitelisted");
  251.         else if (filter && item.typeDescr != "ELEMHIDE")
  252.             type += " " + E("tooltipType").getAttribute("filtered");
  253.         E("tooltipType").setAttribute("value", type);
  254.  
  255.         if (size)
  256.             E("tooltipSize").setAttribute("value", size.join(" x "));
  257.  
  258.         E("tooltipDocDomain").setAttribute("value", item.docDomain);
  259.     }
  260.  
  261.     if (filter)
  262.     {
  263.         setMultilineContent(E("tooltipFilter"), filter.text);
  264.         if (subscriptions.length)
  265.         {
  266.             let sourceElement = E("tooltipFilterSource");
  267.             while (sourceElement.firstChild)
  268.                 sourceElement.removeChild(sourceElement.firstChild);
  269.             for each (let subscription in subscriptions)
  270.                 setMultilineContent(sourceElement, subscription.title, true);
  271.         }
  272.     }
  273.  
  274.     var showPreview = prefs.previewimages && !("tooltip" in item);
  275.     showPreview = showPreview && (item.typeDescr == "IMAGE" || item.typeDescr == "BACKGROUND");
  276.     showPreview = showPreview && (!item.filter || item.filter instanceof abp.WhitelistFilter);
  277.     if (showPreview) {
  278.         // Check whether image is in cache (stolen from ImgLikeOpera)
  279.         if (!cacheSession) {
  280.             var cacheService = Cc["@mozilla.org/network/cache-service;1"].getService(Ci.nsICacheService);
  281.             cacheSession = cacheService.createSession("HTTP", Ci.nsICache.STORE_ANYWHERE, true);
  282.         }
  283.  
  284.         try {
  285.             var descriptor = cacheSession.openCacheEntry(item.location, Ci.nsICache.ACCESS_READ, false);
  286.             descriptor.close();
  287.         }
  288.         catch (e) {
  289.             showPreview = false;
  290.         }
  291.     }
  292.  
  293.     if (showPreview) {
  294.         E("tooltipPreviewBox").hidden = false;
  295.         E("tooltipPreview").setAttribute("src", "");
  296.         E("tooltipPreview").setAttribute("src", item.location);
  297.     }
  298.     else
  299.         E("tooltipPreviewBox").hidden = true;
  300.  
  301.     return true;
  302. }
  303.  
  304. const visual = {
  305.     OTHER: true,
  306.     IMAGE: true,
  307.     SUBDOCUMENT: true
  308. }
  309.  
  310. /**
  311.  * Updates context menu before it is shown.
  312.  */
  313. function fillInContext(/**Event*/ e)
  314. {
  315.     let item, allItems;
  316.     if (treeView.data && !treeView.data.length)
  317.     {
  318.         item = treeView.getDummyTooltip();
  319.         allItems = [item];
  320.     }
  321.     else
  322.     {
  323.         item = treeView.getItemAt(e.clientX, e.clientY);
  324.         allItems = treeView.getAllSelectedItems();
  325.     }
  326.  
  327.     if (!item || ("tooltip" in item && !("filter" in item)))
  328.         return false;
  329.  
  330.     E("contextDisableFilter").hidden = true;
  331.     E("contextEnableFilter").hidden = true;
  332.     E("contextDisableOnSite").hidden = true;
  333.     if ("filter" in item && item.filter)
  334.     {
  335.         let filter = item.filter;
  336.         let menuItem = E(filter.disabled ? "contextEnableFilter" : "contextDisableFilter");
  337.         menuItem.filter = filter;
  338.         menuItem.setAttribute("label", menuItem.getAttribute("labeltempl").replace(/--/, filter.text));
  339.         menuItem.hidden = false;
  340.  
  341.         if (filter instanceof abp.ActiveFilter && !filter.disabled && filter.subscriptions.length && !filter.subscriptions.some(function(subscription) !(subscription instanceof abp.SpecialSubscription)))
  342.         {
  343.             let domain = null;
  344.             try {
  345.                 domain = content.location.host;
  346.                 domain = effectiveTLD.getBaseDomainFromHost(domain);
  347.             } catch (e) {}
  348.  
  349.             if (domain && !filter.isActiveOnlyOnDomain(domain))
  350.             {
  351.                 menuItem = E("contextDisableOnSite");
  352.                 menuItem.item = item;
  353.                 menuItem.filter = filter;
  354.                 menuItem.domain = domain;
  355.                 menuItem.setAttribute("label", menuItem.getAttribute("labeltempl").replace(/--/, domain));
  356.                 menuItem.hidden = false;
  357.             }
  358.         }
  359.     }
  360.  
  361.     E("contextWhitelist").hidden = ("tooltip" in item || !item.filter || item.filter.disabled || item.filter instanceof abp.WhitelistFilter || item.typeDescr == "ELEMHIDE");
  362.     E("contextBlock").hidden = !E("contextWhitelist").hidden;
  363.     E("contextBlock").setAttribute("disabled", "filter" in item && item.filter && !item.filter.disabled);
  364.     E("contextEditFilter").setAttribute("disabled", !("filter" in item && item.filter));
  365.     E("contextOpen").setAttribute("disabled", "tooltip" in item || item.typeDescr == "ELEMHIDE");
  366.     E("contextFlash").setAttribute("disabled", "tooltip" in item || !(item.typeDescr in visual) || (item.filter && !item.filter.disabled && !(item.filter instanceof abp.WhitelistFilter)));
  367.     E("contextCopyFilter").setAttribute("disabled", !allItems.some(function(item) {return "filter" in item && item.filter}));
  368.  
  369.     return true;
  370. }
  371.  
  372. /**
  373.  * Processed mouse clicks on the item list.
  374.  * @param {Event} event
  375.  */
  376. function handleClick(event)
  377. {
  378.     let item = treeView.getItemAt(event.clientX, event.clientY);
  379.     if (event.button == 0 && treeView.getColumnAt(event.clientX, event.clientY) == "state")
  380.     {
  381.         if (item.filter)
  382.             enableFilter(item.filter, item.filter.disabled);
  383.         event.preventDefault();
  384.     }
  385.     else if (event.button == 1)
  386.     {
  387.         openInTab(item);
  388.         event.preventDefault();
  389.     }
  390. }
  391.  
  392. /**
  393.  * Processes double-clicks on the item list.
  394.  * @param {Event} event
  395.  */
  396. function handleDblClick(event)
  397. {
  398.     if (event.button != 0 || treeView.getColumnAt(event.clientX, event.clientY) == "state")
  399.         return;
  400.  
  401.     doBlock();
  402. }
  403.  
  404. /**
  405.  * Opens the item in a new tab.
  406.  */
  407. function openInTab(item)
  408. {
  409.     if (!item)
  410.         item = treeView.getSelectedItem();
  411.     if (!item || item.typeDescr == "ELEMHIDE")
  412.         return;
  413.  
  414.     abp.loadInBrowser(item.location, mainWin);
  415. }
  416.  
  417. function doBlock() {
  418.     if (!abp)
  419.         return;
  420.  
  421.     var item = treeView.getSelectedItem();
  422.     if (!item || item.typeDescr == "ELEMHIDE")
  423.         return;
  424.  
  425.     var filter = null;
  426.     if ("filter" in item && item.filter && !item.filter.disabled)
  427.         filter = item.filter;
  428.  
  429.     if (filter && filter instanceof abp.WhitelistFilter)
  430.         return;
  431.  
  432.     openDialog("chrome://adblockplus/content/ui/composer.xul", "_blank", "chrome,centerscreen,resizable,dialog=no,dependent", window.content, item);
  433. }
  434.  
  435. function editFilter() {
  436.     if (!abp)
  437.         return;
  438.  
  439.     var item = treeView.getSelectedItem();
  440.     if (treeView.data && !treeView.data.length)
  441.         item = treeView.getDummyTooltip();
  442.  
  443.     if (!("filter" in item) || !item.filter)
  444.         return;
  445.  
  446.     if (!("location") in item)
  447.         item.location = undefined
  448.  
  449.     abp.openSettingsDialog(item.location, item.filter);
  450. }
  451.  
  452. function enableFilter(filter, enable) {
  453.     if (!abp)
  454.         return;
  455.  
  456.     filter.disabled = !enable;
  457.     filterStorage.triggerFilterObservers(enable ? "enable" : "disable", [filter]);
  458.     filterStorage.saveToDisk();
  459.  
  460.     treeView.boxObject.invalidate();
  461. }
  462.  
  463. /**
  464.  * Edits the filter to disable it on a particular domain.
  465.  */
  466. function disableOnSite(item, /**Filter*/ filter, /**String*/ domain)
  467. {
  468.     // Generate text for new filter that excludes current domain
  469.     domain = domain.toUpperCase();
  470.     let text = filter.text;
  471.     if (filter instanceof abp.RegExpFilter)
  472.     {
  473.         if (abp.Filter.optionsRegExp.test(text))
  474.         {
  475.             let found = false;
  476.             let options = RegExp.$1.toUpperCase().split(",");
  477.             for (let i = 0; i < options.length; i++)
  478.             {
  479.                 if (/^DOMAIN=(.*)/.test(options[i]))
  480.                 {
  481.                     let domains = RegExp.$1.split("|").filter(function(d) d != domain && d != "~" + domain && d.lastIndexOf("." + domain) != d.length - domain.length - 1);
  482.                     domains.push("~" + domain);
  483.                     options[i] = "DOMAIN=" + domains.join("|");
  484.                     found = true;
  485.                     break;
  486.                 }
  487.             }
  488.             if (!found)
  489.                 options.push("DOMAIN=~" + domain);
  490.  
  491.             text = text.replace(abp.Filter.optionsRegExp, "$" + options.join(",").toLowerCase());
  492.         }
  493.         else
  494.             text += "$domain=~" + domain.toLowerCase();
  495.     }
  496.     else if (filter instanceof abp.ElemHideFilter)
  497.     {
  498.         if (/^([^#]+)(#.*)/.test(text))
  499.         {
  500.             let selector = RegExp.$2;
  501.             let domains = RegExp.$1.toUpperCase().split(",").filter(function(d) d != domain && d != "~" + domain && d.lastIndexOf("." + domain) != d.length - domain.length - 1);
  502.             domains.push("~" + domain);
  503.             text = domains.join(",").toLowerCase() + selector;
  504.         }
  505.         else
  506.             text = "~" + domain.toLowerCase() + text;
  507.     }
  508.  
  509.     if (text == filter.text)
  510.         return;   // Just in case, shouldn't happen
  511.  
  512.     // Insert new filter before the old one and remove the old one then
  513.     let newFilter = abp.Filter.fromText(text);
  514.     if (newFilter.disabled && newFilter.subscriptions.length)
  515.     {
  516.         newFilter.disabled = false;
  517.         filterStorage.triggerFilterObservers("enable", [newFilter]);
  518.     }
  519.     else if (!newFilter.subscriptions.length)
  520.     {
  521.         newFilter.disabled = false;
  522.         filterStorage.addFilter(newFilter, filter);
  523.     }
  524.     filterStorage.removeFilter(filter);
  525.     filterStorage.saveToDisk();
  526.  
  527.     // Update display
  528.     item.filter = null;
  529.     treeView.boxObject.invalidate();
  530. }
  531.  
  532. function copyToClipboard() {
  533.     if (!abp)
  534.         return;
  535.  
  536.     var items = treeView.getAllSelectedItems();
  537.     if (!items.length)
  538.         return;
  539.  
  540.     var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
  541.     clipboardHelper.copyString(items.map(function(item) {return item.location}).join(abp.getLineBreak()));
  542. }
  543.  
  544. function copyFilter() {
  545.     if (!abp)
  546.         return;
  547.  
  548.     var items = treeView.getAllSelectedItems().filter(function(item) {return item.filter});
  549.     if (treeView.data && !treeView.data.length)
  550.         items = [treeView.getDummyTooltip()];
  551.  
  552.     if (!items.length)
  553.         return;
  554.  
  555.     var clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
  556.     clipboardHelper.copyString(items.map(function(item) {return item.filter.text}).join(abp.getLineBreak()));
  557. }
  558.  
  559. function selectAll() {
  560.     if (!abp)
  561.         return;
  562.  
  563.     treeView.selectAll();
  564. }
  565.  
  566. // Saves sidebar's state before detaching/reattaching
  567. function saveState() {
  568.     var focused = document.commandDispatcher.focusedElement;
  569.     while (focused && (!focused.id || !("focus" in focused)))
  570.         focused = focused.parentNode;
  571.  
  572.     // Calculate default position for the detached window
  573.     var boxObject = document.documentElement.boxObject;
  574.     var position = {screenX: boxObject.screenX, screenY: boxObject.screenY, width: boxObject.width, height: boxObject.height};
  575.  
  576.     var params = {
  577.         filter: treeView.filter,
  578.         focus: (focused ? focused.id : null),
  579.         position: position
  580.     };
  581.     abp.setParams(params);
  582. }
  583.  
  584. // closes the sidebar
  585. function doClose()
  586. {
  587.     mainWin.document.getElementById("abp-command-sidebar").doCommand();
  588. }
  589.  
  590. // detaches/reattaches the sidebar
  591. function detach(doDetach)
  592. {
  593.     if (!abp)
  594.         return;
  595.  
  596.     saveState();
  597.  
  598.     // Store variables locally, global variables will go away when we are closed
  599.     let myPrefs = prefs;
  600.     let myMainWin = mainWin;
  601.  
  602.     // Close sidebar and open detached window
  603.     myMainWin.document.getElementById("abp-command-sidebar").doCommand();
  604.     myPrefs.detachsidebar = doDetach;
  605.     myMainWin.document.getElementById("abp-command-sidebar").doCommand();
  606.  
  607.     // Save setting
  608.     myPrefs.save();
  609. }
  610.  
  611. // Returns items size in the document if available
  612. function getItemSize(item)
  613. {
  614.     if (item.filter && !item.filter.disabled && item.filter instanceof abp.BlockingFilter)
  615.         return null;
  616.  
  617.     for each (let node in item.nodes)
  618.     {
  619.         if (node instanceof HTMLImageElement && (node.naturalWidth || node.naturalHeight))
  620.             return [node.naturalWidth, node.naturalHeight];
  621.         else if (node instanceof HTMLElement && (node.offsetWidth || node.offsetHeight))
  622.             return [node.offsetWidth, node.offsetHeight];
  623.     }
  624.     return null;
  625. }
  626.  
  627. // Sort functions for the item list
  628. function sortByAddress(item1, item2) {
  629.     if (item1.location < item2.location)
  630.         return -1;
  631.     else if (item1.location > item2.location)
  632.         return 1;
  633.     else
  634.         return 0;
  635. }
  636.  
  637. function sortByAddressDesc(item1, item2) {
  638.     return -sortByAddress(item1, item2);
  639. }
  640.  
  641. function compareType(item1, item2) {
  642.     if (item1.localizedDescr < item2.localizedDescr)
  643.         return -1;
  644.     else if (item1.localizedDescr > item2.localizedDescr)
  645.         return 1;
  646.     else
  647.         return 0;
  648. }
  649.  
  650. function compareFilter(item1, item2) {
  651.     var hasFilter1 = (item1.filter ? 1 : 0);
  652.     var hasFilter2 = (item2.filter ? 1 : 0);
  653.     if (hasFilter1 != hasFilter2)
  654.         return hasFilter1 - hasFilter2;
  655.     else if (hasFilter1 && item1.filter.text < item2.filter.text)
  656.         return -1;
  657.     else if (hasFilter1 && item1.filter.text > item2.filter.text)
  658.         return 1;
  659.     else
  660.         return 0;
  661. }
  662.  
  663. function compareState(item1, item2) {
  664.     var state1 = (!item1.filter ? 0 : (item1.filter.disabled ? 1 : (item1.filter instanceof abp.WhitelistFilter ? 2 : 3)));
  665.     var state2 = (!item2.filter ? 0 : (item2.filter.disabled ? 1 : (item2.filter instanceof abp.WhitelistFilter ? 2 : 3)));
  666.     return state1 - state2;
  667. }
  668.  
  669. function compareSize(item1, item2) {
  670.     var size1 = getItemSize(item1);
  671.     size1 = size1 ? size1[0] * size1[1] : 0;
  672.  
  673.     var size2 = getItemSize(item2);
  674.     size2 = size2 ? size2[0] * size2[1] : 0;
  675.     return size1 - size2;
  676. }
  677.  
  678. function compareDocDomain(item1, item2)
  679. {
  680.     if (item1.docDomain < item2.docDomain)
  681.         return -1;
  682.     else if (item1.docDomain > item2.docDomain)
  683.         return 1;
  684.     else
  685.         return 0;
  686. }
  687.  
  688. function createSortWithFallback(cmpFunc, fallbackFunc, desc) {
  689.     var factor = (desc ? -1 : 1);
  690.  
  691.     return function(item1, item2) {
  692.         var ret = cmpFunc(item1, item2);
  693.         if (ret == 0)
  694.             return fallbackFunc(item1, item2);
  695.         else
  696.             return factor * ret;
  697.     }
  698. }
  699.  
  700. // Item list's tree view object
  701. var treeView = {
  702.     //
  703.     // nsISupports implementation
  704.     //
  705.  
  706.     QueryInterface: function(uuid) {
  707.         if (!uuid.equals(Ci.nsISupports) &&
  708.                 !uuid.equals(Ci.nsITreeView))
  709.         {
  710.             throw Cr.NS_ERROR_NO_INTERFACE;
  711.         }
  712.     
  713.         return this;
  714.     },
  715.  
  716.     //
  717.     // nsITreeView implementation
  718.     //
  719.  
  720.     selection: null,
  721.  
  722.     setTree: function(boxObject) {
  723.         if (!boxObject)
  724.             return;
  725.  
  726.         this.boxObject = boxObject;
  727.         this.itemsDummy = boxObject.treeBody.getAttribute("noitemslabel");
  728.         this.whitelistDummy = boxObject.treeBody.getAttribute("whitelistedlabel");
  729.  
  730.         var stringAtoms = ["col-address", "col-type", "col-filter", "col-state", "col-size", "col-docDomain", "state-regular", "state-filtered", "state-whitelisted", "state-hidden"];
  731.         var boolAtoms = ["selected", "dummy", "filter-disabled"];
  732.         var atomService = Cc["@mozilla.org/atom-service;1"].getService(Ci.nsIAtomService);
  733.  
  734.         this.atoms = {};
  735.         for each (let atom in stringAtoms)
  736.             this.atoms[atom] = atomService.getAtom(atom);
  737.         for each (let atom in boolAtoms)
  738.         {
  739.             this.atoms[atom + "-true"] = atomService.getAtom(atom + "-true");
  740.             this.atoms[atom + "-false"] = atomService.getAtom(atom + "-false");
  741.         }
  742.  
  743.         this.itemsDummyTooltip = abp.getString("no_blocking_suggestions");
  744.         this.whitelistDummyTooltip = abp.getString("whitelisted_page");
  745.  
  746.         // Check current sort direction
  747.         var cols = document.getElementsByTagName("treecol");
  748.         var sortDir = null;
  749.         for (let i = 0; i < cols.length; i++) {
  750.             var col = cols[i];
  751.             var dir = col.getAttribute("sortDirection");
  752.             if (dir && dir != "natural") {
  753.                 this.sortColumn = col;
  754.                 sortDir = dir;
  755.             }
  756.         }
  757.         if (!this.sortColumn)
  758.         {
  759.             let defaultSort = E("list").getAttribute("defaultSort");
  760.             if (/^(\w+)\s+(ascending|descending)$/.test(defaultSort))
  761.             {
  762.                 this.sortColumn = E(RegExp.$1);
  763.                 if (this.sortColumn)
  764.                 {
  765.                     sortDir = RegExp.$2;
  766.                     this.sortColumn.setAttribute("sortDirection", sortDir);
  767.                 }
  768.             }
  769.         }
  770.  
  771.         if (sortDir)
  772.         {
  773.             this.sortProc = this.sortProcs[this.sortColumn.id + (sortDir == "descending" ? "Desc" : "")];
  774.             E("list").setAttribute("defaultSort", " ");
  775.         }
  776.  
  777.         // Make sure to update the dummy row every two seconds
  778.         setInterval(function(view) {
  779.             if (!view.data || !view.data.length)
  780.                 view.boxObject.invalidateRow(0);
  781.         }, 2000, this);
  782.  
  783.         // Prevent a reference through closures
  784.         boxObject = null;
  785.     },
  786.  
  787.     get rowCount() {
  788.         return (this.data && this.data.length ? this.data.length : 1);
  789.     },
  790.  
  791.     getCellText: function(row, col) {
  792.         col = col.id;
  793.  
  794.         if (col != "type" && col != "address" && col != "filter" && col != "size" && col != "docDomain")
  795.             return "";
  796.  
  797.         if (this.data && this.data.length) {
  798.             if (row >= this.data.length)
  799.                 return "";
  800.  
  801.             if (col == "type")
  802.                 return this.data[row].localizedDescr;
  803.             else if (col == "filter")
  804.                 return (this.data[row].filter ? this.data[row].filter.text : "");
  805.             else if (col == "size")
  806.             {
  807.                 let size = getItemSize(this.data[row]);
  808.                 return (size ? size.join(" x ") : "");
  809.             }
  810.             else if (col == "docDomain")
  811.                 return this.data[row].docDomain;
  812.             else
  813.                 return this.data[row].location;
  814.         }
  815.         else {
  816.             // Empty list, show dummy
  817.             if (row > 0 || (col != "address" && col != "filter"))
  818.                 return "";
  819.  
  820.             if (col == "filter") {
  821.                 var filter = abp.policy.isWindowWhitelisted(window.content);
  822.                 return filter ? filter.text : "";
  823.             }
  824.  
  825.             return (abp.policy.isWindowWhitelisted(window.content) ? this.whitelistDummy : this.itemsDummy);
  826.         }
  827.     },
  828.  
  829.     getColumnProperties: function(col, properties) {
  830.         col = col.id;
  831.  
  832.         if ("col-" + col in this.atoms)
  833.             properties.AppendElement(this.atoms["col-" + col]);
  834.     },
  835.  
  836.     getRowProperties: function(row, properties) {
  837.         if (row >= this.rowCount)
  838.             return;
  839.  
  840.         properties.AppendElement(this.atoms["selected-" + this.selection.isSelected(row)]);
  841.  
  842.         var state;
  843.         if (this.data && this.data.length) {
  844.             properties.AppendElement(this.atoms["dummy-false"]);
  845.  
  846.             let filter = this.data[row].filter;
  847.             if (filter)
  848.                 properties.AppendElement(this.atoms["filter-disabled-" + filter.disabled]);
  849.  
  850.             state = "state-regular";
  851.             if (filter && !filter.disabled)
  852.             {
  853.                 if (filter instanceof abp.WhitelistFilter)
  854.                     state = "state-whitelisted";
  855.                 else if (filter instanceof abp.BlockingFilter)
  856.                     state = "state-filtered";
  857.                 else if (filter instanceof abp.ElemHideFilter)
  858.                     state = "state-hidden";
  859.             }
  860.         }
  861.         else {
  862.             properties.AppendElement(this.atoms["dummy-true"]);
  863.  
  864.             state = "state-filtered";
  865.             if (this.data && abp.policy.isWindowWhitelisted(window.content))
  866.                 state = "state-whitelisted";
  867.         }
  868.         properties.AppendElement(this.atoms[state]);
  869.     },
  870.  
  871.     getCellProperties: function(row, col, properties)
  872.     {
  873.         this.getColumnProperties(col, properties);
  874.         this.getRowProperties(row, properties);
  875.     },
  876.  
  877.     cycleHeader: function(col) {
  878.         col = col.id;
  879.  
  880.         col = E(col);
  881.         if (!col)
  882.             return;
  883.  
  884.         var cycle = {
  885.             natural: 'ascending',
  886.             ascending: 'descending',
  887.             descending: 'natural'
  888.         };
  889.  
  890.         var curDirection = "natural";
  891.         if (this.sortColumn == col)
  892.             curDirection = col.getAttribute("sortDirection");
  893.         else if (this.sortColumn)
  894.             this.sortColumn.removeAttribute("sortDirection");
  895.  
  896.         curDirection = cycle[curDirection];
  897.  
  898.         if (curDirection == "natural")
  899.             this.sortProc = null;
  900.         else
  901.             this.sortProc = this.sortProcs[col.id + (curDirection == "descending" ? "Desc" : "")];
  902.  
  903.         if (this.data)
  904.             this.refilter();
  905.  
  906.         col.setAttribute("sortDirection", curDirection);
  907.         this.sortColumn = col;
  908.  
  909.         this.boxObject.invalidate();
  910.     },
  911.  
  912.     isSorted: function() {
  913.         return this.sortProc;
  914.     },
  915.  
  916.     isContainer: function() {return false},
  917.     isContainerOpen: function() {return false},
  918.     isContainerEmpty: function() {return false},
  919.     getLevel: function() {return 0},
  920.     getParentIndex: function() {return -1},
  921.     hasNextSibling: function() {return false},
  922.     toggleOpenState: function() {},
  923.     canDrop: function() {return false},
  924.     drop: function() {},
  925.     getCellValue: function() {return null},
  926.     getProgressMode: function() {return null},
  927.     getImageSrc: function() {return null},
  928.     isSeparator: function() {return false},
  929.     isEditable: function() {return false},
  930.     cycleCell: function() {},
  931.     performAction: function() {},
  932.     performActionOnRow: function() {},
  933.     performActionOnCell: function() {},
  934.     selectionChanged: function() {},
  935.  
  936.     //
  937.     // Custom properties and methods
  938.     //
  939.  
  940.     boxObject: null,
  941.     atoms: null,
  942.     filter: "",
  943.     data: null,
  944.     allData: [],
  945.     sortColumn: null,
  946.     sortProc: null,
  947.     resortTimeout: null,
  948.     itemsDummy: null,
  949.     whitelistDummy: null,
  950.     itemsDummyTooltip: null,
  951.     whitelistDummyTooltip: null,
  952.  
  953.     sortProcs: {
  954.         address: sortByAddress,
  955.         addressDesc: sortByAddressDesc,
  956.         type: createSortWithFallback(compareType, sortByAddress, false),
  957.         typeDesc: createSortWithFallback(compareType, sortByAddress, true),
  958.         filter: createSortWithFallback(compareFilter, sortByAddress, false),
  959.         filterDesc: createSortWithFallback(compareFilter, sortByAddress, true),
  960.         state: createSortWithFallback(compareState, sortByAddress, false),
  961.         stateDesc: createSortWithFallback(compareState, sortByAddress, true),
  962.         size: createSortWithFallback(compareSize, sortByAddress, false),
  963.         sizeDesc: createSortWithFallback(compareSize, sortByAddress, true),
  964.         docDomain: createSortWithFallback(compareDocDomain, sortByAddress, false),
  965.         docDomainDesc: createSortWithFallback(compareDocDomain, sortByAddress, true)
  966.     },
  967.  
  968.     setData: function(data) {
  969.         var oldRows = this.rowCount;
  970.  
  971.         this.allData = data;
  972.         for each (let item in this.allData)
  973.         {
  974.             if (!item.filter)
  975.                 item.filter = disabledWhitelistMatcher.matchesAny(item.location, item.typeDescr, item.docDomain, item.thirdParty);
  976.             if (!item.filter)
  977.                 item.filter = disabledBlacklistMatcher.matchesAny(item.location, item.typeDescr, item.docDomain, item.thirdParty);
  978.         }
  979.         this.refilter();
  980.  
  981.         this.boxObject.rowCountChanged(0, -oldRows);
  982.         this.boxObject.rowCountChanged(0, this.rowCount);
  983.     },
  984.  
  985.     addItem: function(item) {
  986.         this.allData.push(item);
  987.         if (!item.filter)
  988.             item.filter = disabledWhitelistMatcher.matchesAny(item.location, item.typeDescr, item.docDomain, item.thirdParty);
  989.         if (!item.filter)
  990.             item.filter = disabledBlacklistMatcher.matchesAny(item.location, item.typeDescr, item.docDomain, item.thirdParty);
  991.  
  992.         if (!this.matchesFilter(item))
  993.             return;
  994.  
  995.         var index = -1;
  996.         if (this.sortProc && this.sortColumn && this.sortColumn.id == "size")
  997.         {
  998.             // Sorting by size requires accessing content document, and that's
  999.             // dangerous from a content policy (and we are likely called directly
  1000.             // from a content policy call). Size data will be inaccurate anyway,
  1001.             // delay sorting until later.
  1002.             if (this.resortTimeout)
  1003.                 clearTimeout(this.resortTimeout);
  1004.             this.resortTimeout = setTimeout(function(me)
  1005.             {
  1006.                 if (me.sortProc)
  1007.                     me.data.sort(me.sortProc);
  1008.                 me.boxObject.invalidate();
  1009.             }, 500, this);
  1010.         }
  1011.         else if (this.sortProc)
  1012.             for (var i = 0; index < 0 && i < this.data.length; i++)
  1013.                 if (this.sortProc(item, this.data[i]) < 0)
  1014.                     index = i;
  1015.  
  1016.         if (index >= 0)
  1017.             this.data.splice(index, 0, item);
  1018.         else {
  1019.             this.data.push(item);
  1020.             index = this.data.length - 1;
  1021.         }
  1022.  
  1023.         if (this.data.length == 1)
  1024.             this.boxObject.invalidateRow(0);
  1025.         else
  1026.             this.boxObject.rowCountChanged(index, 1);
  1027.     },
  1028.  
  1029.     /**
  1030.      * Updates the list after a filter or sorting change.
  1031.      */
  1032.     refilter: function()
  1033.     {
  1034.         if (this.resortTimeout)
  1035.             clearTimeout(this.resortTimeout);
  1036.  
  1037.         this.data = this.allData.filter(this.matchesFilter, this);
  1038.  
  1039.         if (this.sortProc)
  1040.             this.data.sort(this.sortProc);
  1041.     },
  1042.  
  1043.     /**
  1044.      * Tests whether an item matches current list filter.
  1045.      * @return {Boolean} true if the item should be shown
  1046.      */
  1047.     matchesFilter: function(item)
  1048.     {
  1049.         if (!this.filter)
  1050.             return true;
  1051.  
  1052.         return (item.location.toLowerCase().indexOf(this.filter) >= 0 ||
  1053.                         (item.filter && item.filter.text.toLowerCase().indexOf(this.filter) >= 0) ||
  1054.                         item.localizedDescr.toLowerCase().indexOf(this.filter) >= 0);
  1055.     },
  1056.  
  1057.     setFilter: function(filter) {
  1058.         var oldRows = this.rowCount;
  1059.  
  1060.         this.filter = filter.toLowerCase();
  1061.         this.refilter();
  1062.  
  1063.         var newRows = this.rowCount;
  1064.         if (oldRows != newRows)
  1065.             this.boxObject.rowCountChanged(oldRows < newRows ? oldRows : newRows, this.rowCount - oldRows);
  1066.         this.boxObject.invalidate();
  1067.     },
  1068.  
  1069.     selectAll: function() {
  1070.         this.selection.selectAll();
  1071.     },
  1072.  
  1073.     getSelectedItem: function() {
  1074.         if (!this.data || this.selection.currentIndex < 0 || this.selection.currentIndex >= this.data.length)
  1075.             return null;
  1076.  
  1077.         return this.data[this.selection.currentIndex];
  1078.     },
  1079.  
  1080.     getAllSelectedItems: function() {
  1081.         let result = [];
  1082.         if (!this.data)
  1083.             return result;
  1084.  
  1085.         let numRanges = this.selection.getRangeCount();
  1086.         for (let i = 0; i < numRanges; i++)
  1087.         {
  1088.             let min = {};
  1089.             let max = {};
  1090.             let range = this.selection.getRangeAt(i, min, max);
  1091.             for (let j = min.value; j <= max.value; j++)
  1092.             {
  1093.                 if (j >= 0 && j < this.data.length)
  1094.                     result.push(this.data[j]);
  1095.             }
  1096.         }
  1097.         return result;
  1098.     },
  1099.  
  1100.     getItemAt: function(x, y)
  1101.     {
  1102.         if (!this.data)
  1103.             return null;
  1104.  
  1105.         var row = this.boxObject.getRowAt(x, y);
  1106.         if (row < 0 || row >= this.data.length)
  1107.             return null;
  1108.  
  1109.         return this.data[row];
  1110.     },
  1111.  
  1112.     getColumnAt: function(x, y)
  1113.     {
  1114.         if (!this.data)
  1115.             return null;
  1116.  
  1117.         let col = {};
  1118.         this.boxObject.getCellAt(x, y, {}, col, {});
  1119.         if (col.value)
  1120.             return col.value.id;
  1121.     },
  1122.  
  1123.     getDummyTooltip: function() {
  1124.         if (!this.data || this.data.length)
  1125.             return null;
  1126.  
  1127.         var filter = abp.policy.isWindowWhitelisted(window.content);
  1128.         if (filter)
  1129.             return {tooltip: this.whitelistDummyTooltip, filter: filter};
  1130.         else
  1131.             return {tooltip: this.itemsDummyTooltip};
  1132.     },
  1133.  
  1134.     selectItem: function(item) {
  1135.         var row = -1;
  1136.         for (var i = 0; row < 0 && i < this.data.length; i++)
  1137.             if (this.data[i] == item)
  1138.                 row = i;
  1139.  
  1140.         if (row < 0 )
  1141.             return;
  1142.  
  1143.         this.selection.select(row);
  1144.         this.boxObject.ensureRowIsVisible(row);
  1145.     }
  1146. }
  1147.